﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace Networking
{
    public class ServerManager
    {
        public int Port { get; set; }
        private TcpListener Listener;
        private List<TcpClient> Clients;
        private List<NetworkStream> NetworkStream;
        private delegate void ReceiveDataEventHandler(int clientid, byte[] data);
        private delegate void ConnectEventHandler(int clientid);
        private event ReceiveDataEventHandler OnReceiveData;
        private event ConnectEventHandler OnClientConnect; // Will be called when client connects

        public ServerManager(int port)
        {
            Port = port; // Assigning Port from the parameter.
            Listener = new TcpListener(new IPEndPoint(IPAddress.Any, Port)); 
            //Initialize the Listener to the port specified on the constructor.
            Clients = new List<TcpClient>(); // Initialize the client sockets list.
            NetworkStream = new List<NetworkStream>(); // Initialize the NetworkStream 
            //which is useful for sending and receiving data.
        }

        public void Start()
        {
            Listener.Start(); // Starts the Listener to listen.
            Listener.BeginAcceptTcpClient(new AsyncCallback(ClientConnect), null);
        }

        public void Stop()
        {
            for (int i = 0; i < Clients.Count; i++) // Closes all the Clients sockets 
            // and the network stream
            {
                Clients[i].Close();
                NetworkStream[i].Close();
            }

            Listener.Stop(); // Stops the listener. The socket which listens and accept 
            //incoming client sockets.
        }

        public void SendDataToClient(int clientid, byte[] data)
        {
            // This function will be used to send byte data to a specific client using the id
            int clientidx = clientid - 1; // Get the array index from the client id.
            NetworkStream[clientidx].Write(data, 0, data.Length);
            NetworkStream[clientidx].Flush();
        }
        
        public void SendDataToAllClient(byte[] data)
        {
            // This function will be used to send data to all client.
            for (int i = 1; i <= Clients.Count; i++) // Foreach client
            {
                SendDataToClient(i, data); // Send data to that client.
            }
        }

        public void SendDataToAllClientExcept(int clientid, byte[] data)
        {
            // This function will be used to send data to all client except that one client using the id.
            for (int i = 1; i <= Clients.Count; i++) // Foreach client
            {
                // If the client is not the client on that id
                if(clientid != i) SendDataToClient(i, data); // Send data to that client.
            }
        }

        private void ClientConnect(IAsyncResult ar)
        {
            // When any client connects.
            TcpClient client = Listener.EndAcceptTcpClient(ar); //Accept the connection.
            Clients.Add(client); // Add on our client list.
            NetworkStream.Add(client.GetStream()); // Add the client's stream on our network stream.

            if (OnClientConnect != null) OnClientConnect(Clients.Count); // Send the client id 
            //which is the last element id of the list and call OnClientConnect

            Thread thrdReceive = new Thread(new ParameterizedThreadStart(ClientReceiveData));
            //Create the thread for receiving data from the client on a new thread process.
            thrdReceive.Start(Clients.Count);
            //Start the thread
            //--
            // Accept the next connection.
            Listener.BeginAcceptTcpClient(new AsyncCallback(ClientConnect), null);
        }

        private void ClientReceiveData(object client)
        {
            int clientid = (int)client; //Cast the client object to clientid which is the 
            //last element id from the list.
            int clientidx = clientid - 1; // The client index of the array.
            int buffersize; // The size of the buffer.
            byte[] databuffer;

            while (true)
            {
                databuffer = new byte[1048]; // Reset/Init the data buffer which will hold the 
                //bytes received.
                buffersize = NetworkStream[clientidx].Read(databuffer, 0, databuffer.Length);
                //Get the bytes and store it on the databuffer as well as get the buffer size.
                Array.Resize(ref databuffer, buffersize);
                //Trim the databuffer array and remove empty bytes.

                //Call the event which will store the clientid and the data.
                if (OnReceiveData != null) OnReceiveData(clientid, databuffer);
            }
        }
    }
}
